package org.xtext.runtime.plan;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.xtext.runtime.formula.Predicate;
import org.xtext.runtime.interpreter.Agent;
import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Term;
import org.xtext.runtime.term.Variable;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.EvaluateTermsVisitor;
import eis.exceptions.ActException;
import eis.exceptions.NoEnvironmentException;
import eis.iilang.Identifier;
import eis.iilang.Numeral;
import eis.iilang.Parameter;
import eis.iilang.Percept;

/**
 * Implements the plan step for invoking an EIS action. 
 * @author rem
 *
 */
public class EISPerformStep implements IPlanStep {
	private String entity;
	private Predicate action;
	
	private class ActionExecutor implements Runnable {
		Agent agent;
		PlanStack planStack;
		
		public ActionExecutor(Agent agent, PlanStack planStack) {
			this.agent = agent;
			this.planStack = planStack;
		}

		public void run() {
			Predicate act = (Predicate) action.accept(new EvaluateTermsVisitor(agent));
			
			// Convert parameter list to EIS parameters types
			LinkedList<Parameter> list = new LinkedList<Parameter>();
			for (ITerm t : act.terms()) {
				Object term = t.value();
				if (Number.class.isInstance(term)) {
					list.add(new Numeral((Number) term));
				} else {
					list.add(new Identifier(term.toString()));
				}
			}
			
			// Invoke the action using EIS
			try {
				Map<String, Percept> percepts = null;
				if (entity == null) {
					percepts = agent.eisService().performAction(agent.name(), new eis.iilang.Action(action.identifier(), list));
				} else {
					percepts = agent.eisService().performAction(entity, new eis.iilang.Action(action.identifier(), list));
				}

				for (Percept percept: percepts.values()) {
					Predicate p = agent.convertPercept(percept);
					if (p != null) agent.addBelief(p);
				}

				agent.notify(planStack, true);
				agent.logAction(toString() + " [SUCCESS]");
			} catch (ActException e) {
				agent.notify(planStack, false);
				if( e.getType() == ActException.NOTSPECIFIC) {
					System.out.println(agent.name() + ": Action Failure (NOTSPECIFIC): " + action);
				} else if( e.getType() == ActException.NOTREGISTERED ) {
					System.out.println(agent.name() + ": Action Failure (NOTREGISTERED): " + action);
				} else if( e.getType() == ActException.NOENTITIES ) {
					System.out.println(agent.name() + ": Action Failure (NOENTITIES): " + action);
				} else if( e.getType() == ActException.WRONGENTITY ) {
					System.out.println(agent.name() + ": Action Failure (WRONGENTITY): " + action);
				} else if( e.getType() == ActException.NOTSUPPORTEDBYENTITY ) {
					System.out.println(agent.name() + ": Action Failure (NOTSUPPORTEDBYENTITY): " + action);
				} else if( e.getType() == ActException.NOTSUPPORTEDBYENVIRONMENT ) {
					System.out.println(agent.name() + ": Action Failure (NOTSUPPORTEDBYENVIRONMENT): " + action);
				} else if( e.getType() == ActException.NOTSUPPORTEDBYTYPE ) {
					System.out.println(agent.name() + ": Action Failure (NOTSUPPORTEDBYTYPE): " + action);
				} else if( e.getType() == ActException.FAILURE ) {
					System.out.println(agent.name() + ": Action Failure (FAILURE): " + action);
				}
				agent.addBelief(new Predicate("failedAction", new ITerm[] { Term.newInstance(action.identifier()) } ));
			} catch (NoEnvironmentException e) {
				System.out.println(agent.name() + ": Action Failed: " + action);
				agent.addBelief(new Predicate("failedAction", new ITerm[] { Term.newInstance(action.identifier()) } ));
			} catch (AssertionError e) {
				System.out.println(agent.name() + ": Action Failed: " + action);
				agent.addBelief(new Predicate("failedAction", new ITerm[] { Term.newInstance(action.identifier()) } ));
			}
		}
	}
	
	public EISPerformStep(String entity, Predicate action) {
		this.entity = entity;
		this.action = action;
	}
	
	public EISPerformStep(Predicate action) {
		this.action = action;
	}
	
	@Override
	public EISPerformStep apply(Bindings set) {
		return new EISPerformStep(entity, action.apply(set));
	}

	@Override
	public void handle(Agent agent, PlanStack planStack) {
		if (agent.eisService() == null) {
			agent.logAction(toString() + " [NO CONNECTED ENVIRONMENT]");
			planStack.fail();
			return;
		}

		new Thread(new ActionExecutor(agent, planStack)).start();
		planStack.suspend();
	}

	@Override
	public String toString(String tabs) {
		String output = tabs + "eis_perform(";
		if (entity != null) output += entity + ",";
		return output + ")";
	}
	
	public String toFullString(String tabs) {
		return toString(tabs);
	}
	
	@Override
	public Set<Variable> variables() {
		return new HashSet<Variable>();
	}

	@Override
	public IPlanStep copy(Map<String, Variable> vars) {
		return new EISPerformStep(entity, action.copy(vars));
	}

	@Override
	public void reset() {
	}
}
